﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.IO;
using System.Timers;

namespace wsserver
{	
	class Resource
	{
		// координаты ресурса
		public int xcoords;
		public int ycoords;
		
		public int type;
		public int count;
		
		public Resource(int val_type, int val_xcoords, int val_ycoords)
		{
			type = val_type;
			count = 0;
			
			xcoords = val_xcoords;
			ycoords = val_ycoords;
		}
	}
	
	class Map
	{
		public string name;
		public int width;
		public int height;
		
		public int[,] locality;		// массив со свойствами местности
		public int[,] resources;	// массив ресурсов
		public int[,] units;		// массив юнитов
		public string[,] flag_res;	// массив флагов (кому принадлежит тот или иной ресурс)
		public int[,] count_res;	// массив счетчиков
		
		
		struct C_Locality
		{
			public const int GRASS = 0;	// трава
			public const int PLAIN = 1;	// равнина
			public const int DESERT = 2;	// пустыня
			public const int JUNGLE = 3;	// джунгли
			public const int SWAMP = 4;	// болото
			public const int WATER = 5;	// вода
			public const int HILL = 6;		// холмы
			public const int MOUNT = 7;	// горы
		}
		
		struct C_Resources
		{
			public const int NONE = 0;		// ничего
			public const int GOLD = 1;		// золото
			public const int SILVER = 2;	// серебро
			public const int CUPRUM = 3;	// медь
			public const int STANNUM = 4;	// олово
			public const int WOOD = 5;		// лес
			public const int FOOD = 6;		// еда
		}
		
		struct C_Units
		{
			public const int NONE = 0;		// ничего
			public const int UNIT = 1;		// юнит
			public const int TOWN = 2;		// город юнита
		}
		
		public Map(string map_name)
		{
			name = map_name;
			string path = @"Maps\" + map_name + ".map";
			
			// Open the file to read from.
	        using (StreamReader sr = File.OpenText(path)) 
			{
			    string s = "";
				string buf = "";
			    while ((buf = sr.ReadLine()) != null) 
			    {
			        s += buf;
			    }
				JObject jobj = JObject.Parse(s);
				string a = (string)jobj["width"];
				int b = Convert.ToInt32(a);
				width = b;
				a = (string)jobj["height"];
				b = Convert.ToInt32(a);
				height = b;
				
				JArray jloc = (JArray)jobj["locality"];
				locality = new int[width, height];
				JArray jres = (JArray)jobj["resources"];
				resources = new int[width, height];
				JArray junt = (JArray)jobj["resources"];
				units = new int[width, height];
				flag_res = new string[width, height];
				JArray jcount_res = (JArray)jobj["count_res"];
				count_res = new int[width, height];
				
				// инициализация массивов, связанных с картой
				for(int i = 0; i < height; i++)
				{
					for(int j = 0; j < width; j++)
					{
						a = (string)jloc[i][j];
						b = Convert.ToInt32(a);
						locality[i, j] = b;
						a = (string)jres[i][j];
						b = Convert.ToInt32(a);
						resources[i, j] = b;
						a = (string)junt[i][j];
						b = Convert.ToInt32(a);
						units[i, j] = b;
						flag_res[i, j] = "NONE";		// изначально ресурсы никому не принадлежат
						a = (string)junt[i][j];
						b = Convert.ToInt32(a);
						count_res[i, j] = b;	
					}
				}
			}
		}
	}
	
	// класс игры KoAbra
	class KoAbra
	{
		struct C_game_status
		{
			public const int NOT_STARTED = 0;
			public const int STARTED = 1;
			public const int PAUSED = 2;
			public const int STOPED = 3;
		}
		
		struct C_Resources
		{
			public const int NONE = 0;		// ничего
			public const int GOLD = 1;		// золото
			public const int SILVER = 2;	// серебро
			public const int CUPRUM = 3;	// медь
			public const int STANNUM = 4;	// олово
			public const int WOOD = 5;		// лес
			public const int FOOD = 6;		// еда
		}
			
		public string name = "";
		public int status = C_game_status.NOT_STARTED;
		
		public User creator;	// создатель
		public Map map;			// карта
		
		public System.Timers.Timer one_step_timer;	// таймер хода
		
		public List<User> Game_users
		{
			get
			{
				return _game_users;
			}
		}
		public List<User> _game_users;
		
		public List<User> A_Game_users
		{
			get
			{
				return _a_game_users;
			}
		}
		public List<User> _a_game_users;
		
		public List<User> B_Game_users
		{
			get
			{
				return _b_game_users;
			}
		}
		public List<User> _b_game_users;
		
		// конструктор игры KoAbra
		public KoAbra(User usr, string val_name)
		{
			_game_users = new List<User>();
			_a_game_users = new List<User>();
			_b_game_users = new List<User>();
			
			one_step_timer = new System.Timers.Timer();
			one_step_timer.Elapsed += new ElapsedEventHandler(OnTimerEvent);
			one_step_timer.Interval = 60000;	// таймер на 60 секунд
			
			creator = usr;
			name = val_name;
			
			_game_users.Add(usr);
		}
		
		// метод добычи ресурса из одной клетки
		public void ExtractTheResource(int val_rate, int i, int j, string val_team)
		{
			// val_rate - коэффициент добычи ресурса
			int ind1 = _a_game_users[i].town._users_resources[j].xcoords;
			int ind2 = _a_game_users[i].town._users_resources[j].ycoords;
			
			if(val_team == "A")
			{
				if(map.count_res[ind1, ind2] >= val_rate)
				{
					_a_game_users[i].town._users_resources[j].count += val_rate;
					map.count_res[ind1, ind2] -= val_rate;
					Console.WriteLine("Добыча ресурса: +" + Convert.ToString(val_rate) + " = " + Convert.ToString(_a_game_users[i].town._users_resources[j].count) + "\n");
					Console.WriteLine("Осталось ресурса: " + Convert.ToString(map.count_res[ind1, ind2]) + "\n");
				}
				// ресурса меньше, чем коэффициент добычи
				else
				{
					_a_game_users[i].town._users_resources[j].count += map.count_res[ind1, ind2];
					map.count_res[ind1, ind2] = 0;
					Console.WriteLine("Добыча ресурса: +" + Convert.ToString(val_rate) + " = " + Convert.ToString(_a_game_users[i].town._users_resources[j].count) + "\n");
					Console.WriteLine("Осталось ресурса: " + Convert.ToString(map.count_res[ind1, ind2]) + "\n");
				}
			}
			if(val_team == "B")
			{
				if(map.count_res[ind1, ind2] >= val_rate)
				{
					_b_game_users[i].town._users_resources[j].count += val_rate;
					map.count_res[ind1, ind2] -= val_rate;
					Console.WriteLine("Добыча ресурса: +" + Convert.ToString(val_rate) + " = " + Convert.ToString(_b_game_users[i].town._users_resources[j].count) + "\n");
					Console.WriteLine("Осталось ресурса: " + Convert.ToString(map.count_res[ind1, ind2]) + "\n");
				}
				// ресурса меньше, чем коэффициент добычи
				else
				{
					_b_game_users[i].town._users_resources[j].count += map.count_res[ind1, ind2];
					map.count_res[ind1, ind2] = 0;
					Console.WriteLine("Добыча ресурса: +" + Convert.ToString(val_rate) + " = " + Convert.ToString(_b_game_users[i].town._users_resources[j].count) + "\n");
					Console.WriteLine("Осталось ресурса: " + Convert.ToString(map.count_res[ind1, ind2]) + "\n");
				}
			}
		}
		
		// метод для обработки события Elapsed таймера
		public void OnTimerEvent(object source, ElapsedEventArgs e)
		{
			//<***************************************
			//=== Сообщение об окончании хода ===
			//*** Server -> Client ***
			//[23.S]	[+][server]
			//{"type": "Action", "action": "Step_completed"}
			for(int i = 0; i < _a_game_users.Count; i++)
			{
				string mess = "{\"type\": \"Action\", \"action\": \"Step_completed\"}";
				_a_game_users[i].SendMessage(mess);
				
				for(int j = 0; j < _a_game_users[i].town._users_resources.Count; j++)
				{
					int ind1 = _a_game_users[i].town._users_resources[j].xcoords;
					int ind2 = _a_game_users[i].town._users_resources[j].ycoords;
					
					// если ресурс не закончился
					if(map.count_res[ind1, ind2] > 0)
					{
						switch(_a_game_users[i].town._users_resources[j].type)
						{
							case C_Resources.GOLD: 
							{
								ExtractTheResource(_a_game_users[i].town.rate_of_res.gold, i, j, "A");							
								break;
							}
							case C_Resources.SILVER:
							{
								ExtractTheResource(_a_game_users[i].town.rate_of_res.silver, i, j, "A");
								break;
							}
							case C_Resources.CUPRUM:
							{
								ExtractTheResource(_a_game_users[i].town.rate_of_res.cuprum, i, j, "A");	
								break;
							}
							case C_Resources.STANNUM:
							{
								ExtractTheResource(_a_game_users[i].town.rate_of_res.stannum, i, j, "A");
								break;
							}
							case C_Resources.WOOD:
							{
								ExtractTheResource(_a_game_users[i].town.rate_of_res.wood, i, j, "A");
								break;
							}
							case C_Resources.FOOD:
							{
								ExtractTheResource(_a_game_users[i].town.rate_of_res.food, i, j, "A");	
								break;
							}
						}
					}
				}
			}
			for(int i = 0; i < _b_game_users.Count; i++)
			{
				string mess = "{\"type\": \"Action\", \"action\": \"Step_completed\"}";
				_b_game_users[i].SendMessage(mess);
				
				for(int j = 0; j < _b_game_users[i].town._users_resources.Count; j++)
				{
					switch(_b_game_users[i].town._users_resources[j].type)
					{
						case C_Resources.GOLD:
						{
							ExtractTheResource(_a_game_users[i].town.rate_of_res.gold, i, j, "B");
							break;
						}
						case C_Resources.SILVER:
						{
							ExtractTheResource(_a_game_users[i].town.rate_of_res.silver, i, j, "B");
							break;
						}
						case C_Resources.CUPRUM:
						{
							ExtractTheResource(_a_game_users[i].town.rate_of_res.cuprum, i, j, "B");
							break;
						}
						case C_Resources.STANNUM:
						{
							ExtractTheResource(_a_game_users[i].town.rate_of_res.stannum, i, j, "B");
							break;
						}
						case C_Resources.WOOD:
						{
							ExtractTheResource(_a_game_users[i].town.rate_of_res.wood, i, j, "B");
							break;
						}
						case C_Resources.FOOD:
						{
							ExtractTheResource(_a_game_users[i].town.rate_of_res.food, i, j, "B");
							break;
						}
					}
				}
			}
			
			//***************************************>
		}
		
		public bool ChooseMap(string map_name)
		{		
			map = new Map(map_name);
			/*if((int)map != 0)
			{
				return(true);
			}
			else
			{
				return(false);
			}*/
			return(true);
		}
		
		struct C_errors
		{
			public static int ALL_RIGHT = 0;
			public static int NOT_ALL_PLAYERS_ARE_READY = 1;
			public static int ONE_TEAM_IS_EMPTY = 2;
			public static int MAP_NOT_SELECTED = 3;
		}
		
		public int Start()
		{
			bool all_right = true;
			int reason = 0;
			
			//<***************************************
			// есть ли пустая команда?
			if(_a_game_users.Count == 0 || _b_game_users.Count == 0)
			{
				all_right = false;
				reason = C_errors.ONE_TEAM_IS_EMPTY;
				goto MARK;
			}
			//***************************************>
			
			//<***************************************
			// все ли игроки готовы к игре?
			bool all_ready = true;
			for(int i = 0; i < _a_game_users.Count; i++)
			{
				if(_a_game_users[i].user_is_ready == false)
				{
					all_ready = false;
				}
			}
			for(int i = 0; i < _b_game_users.Count; i++)
			{
				if(_b_game_users[i].user_is_ready == false)
				{
					all_ready = false;
				}
			}
			if(!all_ready)
			{
				all_right = false;
				reason = C_errors.NOT_ALL_PLAYERS_ARE_READY;
				goto MARK;
			}
			//***************************************>
			
			//<***************************************
			// проверка на то, выбрана ли карта
			if(map == null)
			{
				all_right = false;
				reason = C_errors.MAP_NOT_SELECTED;
				goto MARK;
			}
			//***************************************>
			
		MARK:
			if(all_right)
			{
				status = C_game_status.STARTED;
				ArrangeUnits();		// расставить города по карте
				one_step_timer.Enabled = true;	// запускаем таймер
				return C_errors.ALL_RIGHT;
			}
			else
			{
				return reason;
			}
		}
		
		public int Pause()
		{
			// Куча проверок		
			
			// if(ошибка) return(C_errors.ошибка)
			// else
			status = C_game_status.PAUSED;
			return 0;
		}
		
		public int Stop()
		{
			
			status = C_game_status.STOPED;
			
			return 0;
		}
		
		// метод расстанавливает юниты по карте
		public void ArrangeUnits()
		{
			switch(_a_game_users.Count)
			{
				case 1:
				{
					int x;
					int y;
					x = Convert.ToInt32(map.width/2);
					y = Convert.ToInt32(map.height/2);
					while(true)
					{
						if(map.locality[x, y] < 3)
						{
							break;
						}
						x++;
					}
					_a_game_users[0].town = new Town(_a_game_users[0].Nick, x, y);
					_a_game_users[0].xcoords = x;
					_a_game_users[0].ycoords = y;
					break;
				}
				case 2:
				{
					int x;
					int y;
					// ищем позицию для города первого игрока
					x = Convert.ToInt32(map.width/4);
					y = Convert.ToInt32(map.height/4);
					while(true)
					{
						if(map.locality[x, y] < 3)
						{
							break;
						}
						y++;
					}				
					_a_game_users[0].town = new Town(_a_game_users[0].Nick, x, y);
					_a_game_users[0].xcoords = x;
					_a_game_users[0].ycoords = y;
				
					// ищем позицию для города второго игрока
					x = Convert.ToInt32(map.width/4*3);
					y = Convert.ToInt32(map.height/4*3);
					while(true)
					{
						if(map.locality[x, y] < 3)
						{
							break;
						}
						y--;
					}			
					_a_game_users[1].town = new Town(_a_game_users[1].Nick, x, y);
					_a_game_users[1].xcoords = x;
					_a_game_users[1].ycoords = y;
					break;
				}
			}
			switch(_b_game_users.Count)
			{
				case 1:
				{
					int x;
					int y;
					x = Convert.ToInt32(map.width/2);
					y = Convert.ToInt32(map.height/2);
					while(true)
					{
						if(map.locality[x, y] < 3)
						{
							break;
						}
						x++;
					}
					_b_game_users[0].town = new Town(_b_game_users[0].Nick, x, y);
					_b_game_users[0].xcoords = x;
					_b_game_users[0].ycoords = y;
					break;
				}
				case 2:
				{
					int x;
					int y;
					// ищем позицию для города первого игрока
					x = Convert.ToInt32(map.width/4);
					y = Convert.ToInt32(map.height/4);
					while(true)
					{
						if(map.locality[x, y] < 3)
						{
							break;
						}
						y++;
					}
					_b_game_users[0].town = new Town(_b_game_users[0].Nick, x, y);
					_b_game_users[0].xcoords = x;
					_b_game_users[0].ycoords = y;
				
					// ищем позицию для города второго игрока
					x = Convert.ToInt32(map.width/4*3);
					y = Convert.ToInt32(map.height/4*3);
					while(true)
					{
						if(map.locality[x, y] < 3)
						{
							break;
						}
						y--;
					}			
					_b_game_users[1].town = new Town(_b_game_users[1].Nick, x, y);
					_b_game_users[1].xcoords = x;
					_b_game_users[1].ycoords = y;
					break;
				}
			}
			// все герои при создании игры появляются в городах
			for(int i = 0; i < _a_game_users.Count; i++)
			{
				_a_game_users[i].hero_in_town = true;
			}
			for(int i = 0; i < _b_game_users.Count; i++)
			{
				_b_game_users[i].hero_in_town = true;
			}
				
		}
	}
	
	class PackageResult
	{
		public string type = "Action";
		public string action = "";
		public string status;
		public string reason;
	}
	
	class PackageRemoveGame
	{
		public string type = "Action";
		public string action = "";
		public string name;
	}
	
	// класс формы авторизации/отсоединения в чате
	class PackageChatAuth
	{
		public string type = "YourChatAuth";
		public string login;
		public string status;
		public string reason;
	}
	
	// класс формы сообщения из чата
	class PackageChatMessage
	{
		public string type = "ChatMessage";
		public string login;
		public string message;
	}
	
	// класс, используемый для получения списка игр и игроков
	class PackageListNames
	{
		public string type = "Action";
		public string action = "Send_gamelist";
		public string[] names;
		
		public PackageListNames(int count)
		{
			names = new string[count];
		}
	}
	
	class PackageArrangeUnits
	{
		public string type = "Action";
		public string action = "Arrange_towns";
		public string[] names;
		public int[] xcoords;
		public int[] ycoords;
		
		public PackageArrangeUnits(int count)
		{
			names = new string[count];
			xcoords = new int[count];
			ycoords = new int[count];
		}
	}
}